#include <assert.h>
#include <libut.h>

// XXX Move constants out of frametable
#include "../frametable.h" 
#include "region.h"
#include "include.h"

/* Macro for the highest byte in a given region */
#define REGION_TOP(elt) (elt->base + ((elt->npages * PAGE_SIZE) - 1))

/* The only public region-modification interface */
/* (Everything else is to be used only by VM components) */
int sos_create_region(sos_pcb *pcb, seL4_Word base, seL4_Word npages,
                      page_attrs attrs) {
    int err;
    err = sos_add_region(&(pcb->regions), base, npages, attrs);
    return err;
}

/* Check for overlapping regions */
static int _sanity_check(sos_region_list list) {
    sos_region *elt;
    seL4_Word top;
    /* The region list should always be sorted by base address, so we
       just need to check that each never runs into a neighbour. */
    DL_FOREACH(*list, elt) {
        // Should not be allowing any 0-sized regions in!
        if (elt->npages == 0) return SOS_REGION_INVAL;
        top = REGION_TOP(elt);
        if (elt->next && elt->next->base <= top) {
            // top should be less than base
            return SOS_REGION_INVAL;
        }
    };
    return SOS_REGION_SUCCESS;
}

/* Compare two regions, solely using their base address */
static int _basecmp(sos_region *a, sos_region *b) {
    dprintf(5, "Comparing %p to %p\n", a, b);
    if (a->base < b->base) {
        return -1;
    } else if (a->base == b->base) {
        return 0;
    } else {
        return 1;
    }
}

/* Sort regions in order of their base addresses */
static void _sort(sos_region_list list) {
    DL_SORT((*list), _basecmp);
    return;
}

int sos_add_region(sos_region_list list, seL4_Word base, seL4_Word npages,
                   page_attrs attrs) {
    assert(list && npages);
    // This is a lot slower than it could be, but was implemented in a hurry.
    dprintf(5, "sos_add_region: %lu pages from %p\n", npages, base);

    // Create region structure
    sos_region *new = malloc(sizeof(struct sos_region));
    if (new == NULL) {
        dprintf(1, "sos_add_region: Out of memory (wanted %dB)\n",
                sizeof(struct sos_region));
        return SOS_REGION_OOM;
    }

    // Populate it
    new->base = PAGE_ALIGN(base);
    new->npages = npages;
    new->attrs = attrs;

    dprintf(5, "sos_add_region: Adding region to region list %p\n", list);

    // Whack it at the head of the list, then sort the list
    dprintf(5, "New region is at %p\n", new);
    DL_PREPEND((*list), new);

    dprintf(5, "sos_add_region: Sorting region list\n");
    _sort(list);

    // Sanity-check the list, remove if we've failed
    dprintf(5, "sos_add_region: sanity-checking the region list\n");
    if (_sanity_check(list) != SOS_REGION_SUCCESS) {
        dprintf(1, "Invalid VM region\n");
        DL_DELETE((*list), new);
        free(new);
        return SOS_REGION_INVAL;
    }

    dprintf(5, "sos_add_region: done\n");
    return SOS_REGION_SUCCESS;
}

static inline int _grow_region(sos_region_list list, sos_region *region,
                        seL4_Word new_npages) {
    // Store the old npages
    seL4_Word old_npages = region->npages;

    // Bump the new npages
    region->npages = new_npages;

    // Sanity-check the list
    if (_sanity_check(list) != SOS_REGION_SUCCESS) {
        // On error, restore the old value
        region->npages = old_npages;
        return SOS_REGION_INVAL;
    }

    return SOS_REGION_SUCCESS;
}

/* The caller (pagetable) needs to free up any dangling pages */
static inline int _shrink_region(sos_region_list list, sos_region *region,
                                 seL4_Word new_npages) {
    region->npages = new_npages;
    return SOS_REGION_SUCCESS;
}

int sos_resize_region(sos_region_list list, sos_region *region,
                      seL4_Word new_npages) {
    assert(*list && region);

    if (new_npages == 0) {
        DL_DELETE((*list), region);
        free(region);
        return SOS_REGION_SUCCESS;
    } else if (new_npages > region->npages) {
        // Grow
        return _grow_region(list, region, new_npages);
    } else if (new_npages < region->npages) {
        // Shrink
        return _shrink_region(list, region, new_npages);
    } else {
        // Involutive
        return SOS_REGION_SUCCESS;
    }
}

int sos_remove_region(sos_region_list list, sos_region *region) {
    // Resize to 0
    return sos_resize_region(list, region, 0);
}

int sos_resolve_region(sos_region_list list, seL4_Word proc_vaddr,
                       sos_region **region) {
    assert(region);
    sos_region *ret;
    seL4_Word top;

    dprintf(6, "Resolving region at %p from list at %p\n", proc_vaddr, list);

    // Walk list from the head until proc_vaddr falls inside a region
    DL_FOREACH((*list), ret) {
        dprintf(6, "ret = %p\n", ret);
        top = REGION_TOP(ret);
        if (proc_vaddr >= ret->base && proc_vaddr <= top) {
            (*region) = ret;
            return SOS_REGION_SUCCESS;
        }
    };

    // If we get here, we don't fit in any of the regions - RIP
    (*region) = NULL;
    return SOS_REGION_INVAL;
}

void sos_print_region(sos_region *region) {
    dprintf(5, "region at %p: %d pages, %p - %p\n",
            region,
            region->npages,
            region->base,
            REGION_TOP(region));
}
